package com.ipdatacloud.search;

import java.io.*;
import java.math.BigInteger;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class Search {
    private int[] prefStart;
    private int[] prefEnd;
    private Map<Long, Long> prefStart6 = new HashMap<>();
    private Map<Long, Long> prefEnd6 = new HashMap<>();
    private RandomAccessFile in;
    private byte[] data;
    private final String readMode;
    private String filePath;
    private final int version;
    private long numbers;
    private static Search ipv4InstanceLocation = null;
    private static Search ipv4InstanceScenes = null;
    private static Search ipv4InstanceProxy = null;
    private static Search ipv6Instance = null;

    private Search(String filePath, String readMode, int version) throws IOException {

        this.readMode = readMode;
        this.version = version;
        this.filePath = filePath;
        getObject();
    }

    private void getObject() throws IOException {
        try {
            in = new RandomAccessFile(filePath, "r");
            if (readMode.equals("mem")) {
                data = new byte[(int) in.length()];
                in.read(data);
                in.close();
            }
            if (version == 4) {
                prefStart = new int[256];
                prefEnd = new int[256];
                for (int k = 0; k < 256; k++) {
                    int i = k * 8 + 4;
                    prefStart[k] = (int) UnpackInt4byte(i);
                    prefEnd[k] = (int) UnpackInt4byte(i + 4);

                }
            }
            if (version == 6) {
                prefStart6 = new HashMap<>();
                prefEnd6 = new HashMap<>();
                numbers = UnpackInt4byte(4);
                for (int k = 0; k < numbers; k++) {
                    int i = k * 12 + 4 + 4;
                    prefStart6.put(UnpackInt4byte(i + 8), UnpackInt4byte(i));
                    prefEnd6.put(UnpackInt4byte(i + 8), UnpackInt4byte(i + 4));
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

    public static synchronized Search getIpv4InstanceLocationFile(String filePath) throws IOException {
        return new Search(filePath, "file", 4);
    }

    public static synchronized Search getIpv4InstanceLocationMemory(String filePath) throws IOException {
        if (ipv4InstanceLocation == null)
            ipv4InstanceLocation = new Search(filePath, "mem", 4);
        return ipv4InstanceLocation;
    }

    public static synchronized Search getIpv4InstanceScenesFile(String filePath) throws IOException {
        return new Search(filePath, "file", 4);
    }

    public static synchronized Search getIpv4InstanceScenesMemory(String filePath) throws IOException {
        ipv4InstanceScenes = new Search(filePath, "mem", 4);
        return ipv4InstanceScenes;
    }

    public static synchronized Search getIpv4InstanceProxyFile(String filePath) throws IOException {
        return new Search(filePath, "file", 4);
    }

    public static synchronized Search getIpv4InstanceProxyMemory(String filePath) throws IOException {
        if (ipv4InstanceProxy == null)
            ipv4InstanceProxy = new Search(filePath, "mem", 4);
        return ipv4InstanceProxy;
    }

    public static synchronized Search getIpv6InstanceFile(String filePath) throws IOException {
        return new Search(filePath, "file", 6);
    }

    public static synchronized Search getIpv6InstanceMemory(String filePath) throws IOException {
        if (ipv6Instance == null)
            ipv6Instance = new Search(filePath, "mem", 6);
        return ipv6Instance;
    }

    public String get(String ip) throws IOException {
        try {
            if (version == 4) {
                try {
                    InetAddress inetAddress = InetAddress.getByName(ip);
                    if (!(inetAddress instanceof Inet4Address)) {
                        throw new IllegalArgumentException("Not an IPv4address: " + ip);
                    }
                    ip = inetAddress.getHostAddress();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid IPv4 address: " + ip, e);
                }
                String[] ips = ip.split("\\.");
                int pref = Integer.valueOf(ips[0]);
                long val = ipToLong(ip);
                int low = 0, high = 0;
                if (readMode.equals("mem")) {
                    low = prefStart[pref];
                    high = prefEnd[pref];
                } else if (readMode.equals("file")) {
                    low = (int) UnpackInt4byte(pref * 8 + 4);
                    high = (int) UnpackInt4byte(pref * 8 + 4 + 4);
                }
                long cur = low == high ? low : searchV4(low, high, val);
                return getAddrV4((int) cur);
            } else if (version == 6) {
                try {
                    InetAddress inetAddress = InetAddress.getByName(ip);
                    if (!(inetAddress instanceof Inet6Address)) {
                        throw new IllegalArgumentException("Not an IPv6 address: " + ip);
                    }
                    ip = inetAddress.getHostAddress();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid IPv6 address: " + ip, e);
                }
                if (ip.contains("%")) {
                    ip = ip.split("%")[0];
                }
                String[] ips = ip.split(":");
                long pref = Long.parseLong(ips[0], 16);
                BigInteger val = stringToBigInt(ip);

                if (prefStart6.get(pref) == null || prefEnd6.get(pref) == null) {
                    return "保留|保留|||||||||||||||||";
                }
                long low = prefStart6.get(pref), high = prefEnd6.get(pref);
                long cur = low == high ? low : searchV6(low, high, val);
                return getAddrV6((int) cur);
            } else {
                return "不支持的ip版本";
            }
        } catch (Exception e) {
            return e.getMessage();
        }

    }

    public String get(InetAddress addr) throws IOException {
        String ip = "";
        if (addr instanceof Inet4Address) {
            if (version != 4) {
                throw new IllegalArgumentException("Not IPv4 Database");
            }
            ip = addr.getHostAddress();
            String[] ips = ip.split("\\.");
            int pref = Integer.valueOf(ips[0]);
            long val = ipToLong(ip);
            int low = 0, high = 0;
            if (readMode.equals("mem")) {
                low = prefStart[pref];
                high = prefEnd[pref];
            } else if (readMode.equals("file")) {
                low = (int) UnpackInt4byte(pref * 8 + 4);
                high = (int) UnpackInt4byte(pref * 8 + 4 + 4);
            }
            long cur = low == high ? low : searchV4(low, high, val);
            return getAddrV4((int) cur);
        } else if (addr instanceof Inet6Address) {
            if (version != 6) {
                throw new IllegalArgumentException("Not IPv4 Database");
            }
            ip = addr.getHostAddress();
            if (ip.contains("%")) {
                ip = ip.split("%")[0];
            }
            String[] ips = ip.split(":");
            long pref = Long.parseLong(ips[0], 16);
            BigInteger val = stringToBigInt(ip);

            if (prefStart6.get(pref) == null || prefEnd6.get(pref) == null) {
                return "保留|保留|||||||||||||||||";
            }
            long low = prefStart6.get(pref), high = prefEnd6.get(pref);
            long cur = low == high ? low : searchV6(low, high, val);
            return getAddrV6((int) cur);
        } else {
            return "不支持的ip版本";
        }

    }

    private int searchV4(int low, int high, long k) throws IOException {
        int M = 0;

        while (low <= high) {
            int mid = (low + high) >> 1;

            int p = 2052 + (mid * 9);
            long endipNum = UnpackInt4byte(p);

            if (endipNum >= k) {
                M = mid;
                if (mid == 0) {
                    break;
                }
                high = mid - 1;
            } else
                low = mid + 1;
        }
        return M;
    }

    private int searchV6(long low, long high, BigInteger k) throws IOException {
        int M = 0;
        byte[] record = new byte[50];
        while (low <= high) {
            int mid = (int) (low + high) >> 1;
            int p = (int) numbers * 12 + 4 + 4 + (mid * 55);

            BigInteger endipNum;
            if (readMode.equals("file")) {
                synchronized (this) {
                    in.seek(p);
                    in.read(record);
                    endipNum = new BigInteger(new String(record).replaceAll("\\*", ""));
                }
            } else if (readMode.equals("mem")) {
                endipNum = new BigInteger(new String(Arrays.copyOfRange(data, p, p + 50)).replaceAll("\\*", ""));
            } else {
                throw new RuntimeException("unsupported reading mode");
            }
            if (endipNum.compareTo(k) == 0 || endipNum.compareTo(k) == 1) {
                M = mid;
                if (mid == 0) {
                    break;
                }
                high = mid - 1;
            } else
                low = mid + 1;
        }
        return M;
    }

    private String getAddrV4(int cur) throws IOException {
        int p = 2052 + (cur * 9);

        int offset = (int) UnpackInt4byte(p + 4);
        int length = (int) UnpackInt1byte(p + 8);

        if (readMode.equals("mem")) {
            return new String(Arrays.copyOfRange(data, offset, offset + length));
        } else if (readMode.equals("file")) {
            synchronized (this) {
                byte[] record = new byte[length];
                in.seek(offset);
                in.read(record);
                return new String(record);
            }
        } else {
            throw new RuntimeException("unsupported reading method");
        }

    }

    private String getAddrV6(int cur) throws IOException {
        int p = (int) numbers * 12 + 4 + 4 + (cur * 55);
        int offset = (int) UnpackInt4byte(p + 50);
        int length = (int) UnpackInt1byte(p + 50 + 4);
        if (readMode.equals("mem")) {
            return new String(Arrays.copyOfRange(data, offset, (offset + length)));
        } else if (readMode.equals("file")) {
            synchronized (this) {
                byte[] record = new byte[length];
                in.seek(offset);
                in.read(record);
                return new String(record);
            }
        } else {
            throw new RuntimeException("unsupported reading method");
        }
    }

    private long ipToLong(String ip) {
        long result = 0;
        String[] d = ip.split("\\.");
        for (String b : d) {
            result <<= 8;
            result |= Long.parseLong(b) & 0xff;
        }
        return result;
    }

    private static BigInteger stringToBigInt(String ipInString) {
        ipInString = ipInString.replace(" ", "");
        byte[] bytes;
        if (ipInString.contains(":"))
            bytes = ipv6ToBytes(ipInString);
        else
            bytes = ipv4ToBytes(ipInString);
        return new BigInteger(bytes);
    }

    public static byte[] ipv6ToBytes(String ipv6) {
        try {
            // 去除可能的区域索引（如fe80::1%eth0 -> fe80::1）
            String pureIpv6 = ipv6.split("%")[0];
            // 使用InetAddress解析
            InetAddress inetAddress = InetAddress.getByName(pureIpv6);
            if (!(inetAddress instanceof Inet6Address)) {
                throw new IllegalArgumentException("Not an IPv6 address: " + ipv6);
            }
            // 直接返回16字节的地址
            return inetAddress.getAddress();
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("Invalid IPv6 address: " + ipv6, e);
        }
    }

    public static byte[] ipv4ToBytes(String ipv4) {
        try {
            InetAddress address = InetAddress.getByName(ipv4);
            return address.getAddress(); // 返回 byte[4]
        } catch (UnknownHostException e) {
            System.err.println("无效的 IP 地址: " + ipv4);
            throw new IllegalArgumentException("Invalid IPv4 address: " + ipv4, e); // 或者抛出 RuntimeException(e);
        }
    }

    private long UnpackInt4byte(int offset) throws IOException {
        if (readMode.equals("mem")) {
            return (data[offset] & 0xFFL) | ((data[offset + 1] << 8) & 0xFF00L) | ((data[offset + 2] << 16) & 0xFF0000L)
                    | ((data[offset + 3] << 24) & 0xFF000000L);
        } else if (readMode.equals("file")) {
            synchronized (this) {
                byte[] record = new byte[4];
                in.seek(offset);
                in.read(record);
                return (record[0] & 0xFFL) | ((record[1] << 8) & 0xFF00L) | ((record[2] << 16) & 0xFF0000L)
                        | ((record[3] << 24) & 0xFF000000L);
            }
        } else {
            throw new RuntimeException("unsupported reading method");
        }
    }

    private long UnpackInt1byte(int offset) throws IOException {
        if (readMode.equals("mem")) {
            return data[offset] & 0xFFL;
        } else if (readMode.equals("file")) {
            synchronized (this) {
                byte[] record = new byte[1];
                in.seek(offset);
                in.read(record);
                return (record[0] & 0xFFL);
            }
        } else {
            throw new RuntimeException("unsupported reading method");
        }
    }

    /**
     * 从给定链接中下载文件并保存在所给位置
     *
     * @param downUrl       文件下载链接
     * @param localFilePath 文件保存位置，与原文件的位置一致，否则无法检测到文件变动并自动重载
     */
    public static String updateDatabase(String downUrl, String localFilePath) {
        HttpURLConnection httpConn = null;
        try {
            URL url = new URL(downUrl);
            httpConn = (HttpURLConnection) url.openConnection();
            int responseCode = httpConn.getResponseCode();
            String savePath = "";

            // 检测状态码
            if (responseCode == HttpURLConnection.HTTP_OK) {
                String[] path = url.getPath().split("/");
                savePath = download(path[path.length - 1], httpConn, localFilePath);
            } else if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
                // 如果状态码为302，从Location中提取跳转地址
                String realUrl = httpConn.getHeaderField("Location");
                if (realUrl != null) {
                    url = new URL(realUrl);
                    httpConn = (HttpURLConnection) url.openConnection();
                    if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                        // 根据真实地址获取文件名
                        String[] path = url.getPath().split("/");
                        savePath = download(path[path.length - 1], httpConn, localFilePath);
                    } else {
                        System.out.println("download file failed,status_code: " + httpConn.getResponseCode());
                    }
                } else {
                    System.out.println("redirect address is empty, unable to complete the download.");
                }
            } else {
                System.out.println("download file failed,status_code: " + responseCode);
            }

            // 处理下载的文件
            if (savePath != null && !savePath.isEmpty() && savePath.endsWith(".zip")) {
                unzip(savePath, localFilePath);
                System.out.println("Extract file success: " + savePath);
            } else {
                System.out.println("not a zip file: " + savePath);
            }
            return "download success";
        } catch (IOException e) {
            System.err.println("IO exception occurred: " + e.getMessage());
            return "IO exception occurred: " + e.getMessage();
        } catch (Exception e) {
            System.err.println("exception occurred: " + e.getMessage());
            return "exception occurred: " + e.getMessage();
        } finally {
            if (httpConn != null) {
                httpConn.disconnect();
            }
        }
    }

    private static String download(String fileName, HttpURLConnection httpConn, String localFilePath)
            throws IOException {
        String disposition = httpConn.getHeaderField("Content-Disposition");

        if (disposition != null) {
            // Extracts file name from header field
            int index = disposition.indexOf("filename=");
            if (index > 0) {
                fileName = disposition.substring(index + 10, disposition.length() - 1);
            }
        }
        // 从http链接获得文件流
        InputStream inputStream = httpConn.getInputStream();
        String saveFilePath = localFilePath + File.separator + fileName;
        // 保存文件
        Path savePath = Paths.get(saveFilePath);
        Files.copy(inputStream, savePath, StandardCopyOption.REPLACE_EXISTING);

        inputStream.close();
        System.out.println("download success: " + localFilePath);
        return saveFilePath;
    }

    private static void unzip(String zipFilePath, String destDirectory) throws IOException {
        File destDir = new File(destDirectory);
        if (!destDir.exists()) {
            destDir.mkdir();
        }
        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
        ZipEntry entry = zipIn.getNextEntry();
        // 遍历zip文件中的所有条目
        while (entry != null) {
            String filePath = destDirectory + File.separator + entry.getName();
            if (!entry.isDirectory()) {
                // 如果是文件，则提取文件
                extractFile(zipIn, filePath);
            } else {
                // 如果是目录，则创建目录
                File dir = new File(filePath);
                dir.mkdirs();
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
        zipIn.close();
    }

    /**
     * 从ZIP输入流中提取文件
     *
     * @param zipIn    ZIP输入流
     * @param filePath 目标文件路径
     * @throws IOException 如果发生I/O错误
     */
    private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[4096];
        int read = 0;
        while ((read = zipIn.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    }


}
